#ifndef __POINT_H__
#define __POINT_H__

#include <iostream>
#include <fstream>
#include <initializer_list>
#include <iomanip>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <concepts>

template <size_t DIM, typename T = double>
concept point_type = (std::is_integral<T>::value ||
					  std::is_same<T, float>::value ||
					  std::is_same<T, double>::value ||
					  std::is_same<T, long double>::value) &&
					  (DIM > 0);

template <size_t DIM, typename T = double>
requires point_type<DIM, T>
class Point
{
private:
	T coord[DIM];

public:
	Point();
	Point(std::initializer_list<T> initl);
	Point(const Point<DIM, T>& point);
	~Point() = default;
	
	const T& operator[](size_t i) const;
	T& operator[](size_t i);
	const Point<DIM, T>& operator=(const Point<DIM, T>& point);
	const Point<DIM, T> operator+(const Point<DIM, T>& point) const;
	const Point<DIM, T> operator-(const Point<DIM, T>& point) const;
	const Point<DIM, T> operator*(T a) const;
	const Point<DIM, T>& operator+=(const Point<DIM, T>& point);
};



template <size_t DIM, typename T>
Point<DIM, T>::Point()
{
	for (size_t i = 0; i < DIM; ++i)
		coord[i] = 0;
}

template <size_t DIM, typename T>
Point<DIM, T>::Point(std::initializer_list<T> initl)
{
	std::size_t i = 0, size = std::min((std::size_t)DIM, initl.size());
	for (; i < size; ++i)
		coord[i] = initl.begin()[i];
	for (; i < DIM; ++i)
		coord[i] = 0;
}

template <size_t DIM, typename T>
Point<DIM, T>::Point(const Point<DIM, T>& point)
{
	for (size_t i = 0; i < DIM; ++i)
		coord[i] = point.coord[i];
}

template <size_t DIM, typename T>
const T& Point<DIM, T>::operator[](size_t i) const
{
	assert(i < DIM);
	return coord[i];
}

template <size_t DIM, typename T>
T& Point<DIM, T>::operator[](size_t i)
{
	assert(i < DIM);
	return coord[i];
}

template <size_t DIM, typename T>
const Point<DIM, T>& Point<DIM, T>::operator=(const Point<DIM, T>& point)
{
	if (this != &point) {
		for (size_t i = 0; i < DIM; ++i)
			coord[i] = point.coord[i];
	}
	return *this;
}

template <size_t DIM, typename T>
const Point<DIM, T> Point<DIM, T>::operator+(const Point<DIM, T>& point) const
{
	Point<DIM, T> p;
	for (size_t i = 0; i < DIM; ++i)
		p[i] = coord[i] + point[i];
	return p;
}

template <size_t DIM, typename T>
const Point<DIM, T> Point<DIM, T>::operator-(const Point<DIM, T>& point) const
{
	Point<DIM, T> p;
	for (size_t i = 0; i < DIM; ++i)
		p[i] = coord[i] - point[i];
	return p;
}

template <size_t DIM, typename T>
const Point<DIM, T> Point<DIM, T>::operator*(T a) const
{
	Point<DIM, T> p;
	for (size_t i = 0; i < DIM; ++i)
		p[i] = coord[i] * a;
	return p;
}

template <size_t DIM, typename T>
const Point<DIM, T>& Point<DIM, T>::operator+=(const Point<DIM, T>& point)
{
	for (size_t i = 0; i < DIM; ++i)
		coord[i] += point.coord[i];
	return *this;
}

template <size_t Dim, typename T1 = double>
std::ostream& operator<<(std::ostream& os, const Point<Dim, T1>& point)
{
	os << std::fixed << std::setprecision(3);
	os << "(";
	for (size_t i = 0; i < Dim - 1; ++i)
		os << point[i] << ", ";
	os << point[Dim-1] << ")";

	return os;
}

template <size_t Dim, typename T2, typename T1 = double>
const Point<Dim, T1> operator*(T2 a, const Point<Dim, T1>& point)
{
	Point<Dim, T1> p;
	for (size_t i = 0; i < Dim; ++i)
		p[i] = a * point[i];
	return p;
}

template <size_t Dim, typename T1 = double>
double MaxNorm(const Point<Dim, T1>& point)
{
	double max = 0, tmp = 0;
	for (size_t i = 0; i < Dim; ++i) {
		tmp = fabs(point[i]);
		if (tmp > max)
			max = tmp;
	}
	return max;
}

template <size_t Dim, typename T1 = double>
double SquareNorm(const Point<Dim, T1>& point)
{
	double sum = 0;
	for (size_t i = 0; i < Dim; ++i)
		sum += point[i] * point[i];
	return sqrt(sum);
}

#endif
